﻿#region Namespace Imports


// The following are standard imports for ASPX pages
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

// Simplifies access to generic collection types
using System.Collections.Generic;

// Access to localization/globalization types
using System.Globalization;

// For access to some declarative security types
using System.Security.Permissions;

// The following simplify access to required SharePoint types
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;

// Alias the assembly resources to shorten references.
using res = SPMcDonough.Moss.BlobCacheFarmFlush.Properties.Resources;


#endregion Namespace Imports


namespace SPMcDonough.Moss.BlobCacheFarmFlush
{


    /// <summary>
    /// This is the code-behind class for the BlobCacheFarmFlush.aspx page.  It
    /// is here that UI manipulation is carried out; in addition, the code that
    /// is responsible for updating the appropriate SPWebApplication property that
    /// triggers the BLOB cache flush is located and executed here.
    /// </summary>
    public class BlobCacheFarmFlushPage : LayoutsPageBase
    {


        #region Control Declarations


        // The following are the OK and Cancel buttons that appear at the bottom
        // right on the page.
        protected Button CancelButton;
        protected Button OkButton;
        
        // The following is the checkbox (and housing form) that an admin checks 
        // in order to indicate that a BLOB cache flush for the farm is desired.
        protected InputFormControl CheckSectionInputForm;
        protected InputFormCheckBox ResetBlobCacheCheckBox;

        // The following is used for the cancel section that is activated when a
        // flush is pending.
        protected Button StopFlushButton;
        protected Label StopFlushLabel;
        protected InputFormControl StopSectionInputForm;

        // This label is set to identify (to the user) why normal access to the page
        // isn't being granted.
        protected Label ReasonForBlockLabel;

        // The following list is used to display each of the URLs that would be
        // affected/impacted in the event of a BLOB cache flush.
        protected BulletedList AffectedUrlsList;

        // These panels control which controls are visible on the page.  Basically,
        // the dividing line is valid access (controls in pnlAppropriateAccess) versus
        // inappropriate access (pnlInappropriateAccess).
        protected Panel AppropriateAccessPanel;
        protected Panel InappropriateAccessPanel;


        #endregion Control Declarations


        #region Overrides (LayoutsBasePage)


        /// <summary>
        /// Property accessor that is used to determine if the caller must be a
        /// site administrator to access the page.  In the case of this page, we
        /// require that the user be a site admin to perform a flush.
        /// </summary>
        protected override Boolean RequireSiteAdministrator
        {
            [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
            get
            {
                return true;
            }
        }


        #endregion Overrides (LayoutsBasePage)


        #region Events (Form and Control)


        /// <summary>
        /// The cancel button was clicked.  We simply need to navigate back up to the
        /// site collection settings page.
        /// </summary>
        protected void CancelButton_Click(Object sender, EventArgs e)
        {
            // Simply redirect the user, but account for the fact that the
            // root of the site collection might be lower in the overall URL
            // hierarchy.  Using the Redirect method of the SPUtility class
            // simplifies redirection to the LAYOUTS pages in the current site collection.
            //
            // Thanks to Ton Stegeman for describing the operation of this method in his
            // blog post (http://www.tonstegeman.com/Blog/Lists/Posts/Post.aspx?ID=102)
            SPUtility.Redirect(res.PAGE_REDIRECT_ON_CANCEL,
                               SPRedirectFlags.Static | SPRedirectFlags.RelativeToLayoutsPage,
                               HttpContext.Current);
        }


        /// <summary>
        /// The user pressed the ok button, so we need to see if the checkbox was
        /// checked.  If so, we'll attempt a BLOB cache flush.  If the checkbox wasn't
        /// checked, then we'll process as if this was a cancel.
        /// </summary>
        protected void OkButton_Click(Object sender, EventArgs e)
        {
            // Examine the checkstate of the checkbox to see if the user checked it.
            if (ResetBlobCacheCheckBox.Checked)
            {
                // The checkbox is checked, so we need to carry out the flush.  This
                // is done with the following method.
                SetFlushFlagStatus(true);
            }
        }


        /// <summary>
        /// The Page is being loaded, so we need to take care of any initialization
        /// and checks required.  Within this event handler, we'll be ensuring that
        /// the call to the page is appropriate (i.e., the Feature supplying the page
        /// is actually active on the site); we'll also be setting up the display for
        /// the user -- either the flush controls or an error tag.
        /// </summary>
        protected void Page_Load(Object sender, EventArgs e)
        {
            // First off: make sure that the BlobCacheFarmFlush Feature is active
            // on the site collection.
            if (IsFeatureActiveOnCurrentSiteCollection(res.FEATURE_ADMIN_ID))
            {
                // Is this a postback?  If not (and this is the first time through the
                // page), then there are a few initial control properties that need to
                // be set.
                if (!IsPostBack)
                {
                    // One of the prep items we have to take care of is list of URLs that
                    // would be impacted by a cache flush.  Retrieve the list and then add
                    // each one to the bulleted list.
                    List<String> affectedUrls = LookupUrlsForCurrentSiteCollection();
                    foreach (String currentUrl in affectedUrls)
                    {
                        // Add the current URL to the bulleted list.
                        AffectedUrlsList.Items.Add(currentUrl);
                    }

                    // Set the caption that users would see for cancellations.
                    StopFlushLabel.Text = String.Format(CultureInfo.InvariantCulture,
                                                        res.FORMAT_PAGE_MSG_STOP_FLUSH,
                                                        res.TIMER_JOB_SCHEDULE_INTERVAL);

                    // Ensure that the full controls panel is visible and that the 
                    // message panel (for inappropriate access) is hidden.
                    AppropriateAccessPanel.Visible = true;
                    InappropriateAccessPanel.Visible = false;
                }

                // Regardless of whether or not this is a postback, set the control
                // status based on whether or not a flush is pending.  State hasn't
                // changed within the context of the call yet, so the parameter passed
                // is FALSE.
                SetFormControlState(false);
            }

            else
            {
                // The BlobCacheFarmFlush feature isn't even active on the current
                // site collection.  In all likelihood, the user tried to navigate
                // directly to this page.  Display a message indicating the problem.
                ReasonForBlockLabel.Text = res.PAGE_MSG_ADMINFEATURENOTACTIVE;

                // Set panels to display only the message panel.
                AppropriateAccessPanel.Visible = false;
                InappropriateAccessPanel.Visible = true;
            }
        }


        /// <summary>
        /// To be able to click this button, a flush must be pending and the user has
        /// indicated that they'd like to cancel it.  It is the responsibility of this
        /// method to toggle the flush flag on the site collection back to a state that
        /// won't result in a flush.
        /// </summary>
        protected void StopFlushButton_Click(Object sender, EventArgs e)
        {
            // In order for this control to be visible and clickable, a flush must
            // be pending.  Simply make the call that will carry out the flush
            // cancellation.
            SetFlushFlagStatus(false);
        }


        #endregion Events (Form and Control)


        #region Methods (Private)


        /// <summary>
        /// This method is used to determine if a Feature is active on the current
        /// site collection.  This method is used 
        /// </summary>
        /// <param name="featureId">A String that contains the ID of the feature for
        /// which the check of the site collection should be performed.</param>
        /// <returns>TRUE if the Feature specified by the <paramref name="featureId"/> 
        /// is active on the site collection, FALSE if not.</returns>
        private Boolean IsFeatureActiveOnCurrentSiteCollection(String featureId)
        {
            // Establish the return value; assume that the Feature is not active
            // by default.
            Boolean isActive = false;
            
            // Convert the supplied string into a usable GUID.  On the off-chance
            // that the String isn't valid, we'll simply assign an empty GUID
            // value.  This will be checked in the conditional test that follows.
            Guid featureGuid;
            try
            {
                featureGuid = new Guid(featureId);
            }
            catch
            {
                featureGuid = Guid.Empty;
            }

            // Assuming we've got a usable GUID value and Site, proceed with lookup
            // attempts to see if the Feature is active on the site collection.
            if (featureGuid != Guid.Empty && this.Site != null)
            {
                // Iterate through each of the Features that is on the current site
                // to see if we can find a match.
                foreach (SPFeature currentFeature in this.Site.Features)
                {
                    // If we find a match, we'll set the method-level flag accordingly
                    // and hop out.
                    if (currentFeature.DefinitionId == featureGuid)
                    {
                        isActive = true;
                        break;
                    }
                }
            }

            // Return the flag indicating whether or not the specified Feature is
            // active on the current site collection.
            return isActive;
        }


        /// <summary>
        /// This method is used to determine if a flush is pending for the current
        /// site collection.  Understanding whether or not a flush is being requested
        /// allows this code-behind to drive control behavior around whether or not
        /// flush cancellation controls should be enabled or disabled.
        /// </summary>
        /// <returns>TRUE if a flush has been requested for the current site collection
        /// and is waiting to be processed, FALSE if not.</returns>
        private Boolean IsFlushPending()
        {
            // Setup the return value and assume a flush isn't pending unless we
            // discover otherwise.
            Boolean pendingFlag = false;

            // Proceed only if we have a valid site collection reference.
            if (this.Site != null)
            {
                // Grab a reference to the root web and test for the presence of the
                // flush flag on the web.
                SPWeb rootWeb = this.Site.RootWeb;
                if (rootWeb.Properties.ContainsKey(res.ROOTWEB_PROPERTY_FLUSHFLAG))
                {
                    // The flush flag is present on the web, so we'll see if we can get
                    // a value out that is a valid boolean.  If the conversion succeeds,
                    // we'll have the desired value.  If the conversion isn't possible
                    // (e.g., perhaps a null reference is assigned), then the Try... 
                    // operation will yield a FALSE.
                    Boolean.TryParse(rootWeb.Properties[res.ROOTWEB_PROPERTY_FLUSHFLAG], out pendingFlag);
                }                
            }
        
            // Return the result.
            return pendingFlag;        
        }


        /// <summary>
        /// This method is used to obtain a list of the URLs that are associated with
        /// web application housing the current site collection.  When a BLOB cache is
        /// performed, it is handled at the SPWebApplication level; therefore, we need
        /// a list of the base URLs for display to the user (so that they'll understand
        /// which sites will be impacted).
        /// </summary>
        /// <returns>A generic List object of Strings.  Each String in the list is a
        /// URL that can be displayed to the user.</returns>
        private List<String> LookupUrlsForCurrentSiteCollection()
        {
            // Build the object that will be returned and establish a reference to
            // the current site.
            List<String> urlList = new List<string>();
            SPSite currentSite = this.Site;

            // Proceed with processing only if we have a valid site reference.
            if (currentSite != null)
            {
                // We need a reference to the web app in which the site is housed,
                // and then we need to iterate through the AlternateUrl collection
                // to build our list.
                SPWebApplication housingWebApp = currentSite.WebApplication;
                foreach (SPAlternateUrl currentUrl in housingWebApp.AlternateUrls)
                {
                    // Add each URL in the collection to the list being constructed.
                    urlList.Add(currentUrl.Uri.ToString());
                }
            }

            // Return the list of URLs.
            return urlList;
        }


        /// <summary>
        /// This method examines the SPWebApplication housing the current site collection
        /// and pulls out the value that is stored for the specified property value.
        /// </summary>
        /// <param name="propertyName">A String containing the name of the desired property
        /// as it appears in the web application's Properties collection.</param>
        /// <returns>An object that is the value for the <paramref name="propertyName"/>.
        /// Properties that haven't been assigned a value are returned as null references.</returns>
        private Object RetrieveWebAppPropertyValue(String propertyName)
        {
            // Setup our return value and grab a reference to the current SPSite.
            Object propertyValue = null;
            SPSite currentSite = this.Site;

            // Proceed provided we've got a valid SPSite
            if (currentSite != null)
            {
                // Grab a reference to the parent web application and attempt to retrieve
                // the requested property value.
                SPWebApplication parentWebApp = currentSite.WebApplication;
                propertyValue = parentWebApp.Properties[propertyName];
            }

            // Return the result of the lookup.
            return propertyValue;
        }


        /// <summary>
        /// This method simply grabs the RootWeb for the current site collection and
        /// makes sure that its property bag contains a flag to signal to the timer
        /// job that a flush should take place or be cancelled.
        /// </summary>
        private void SetFlushFlagStatus(Boolean enableFlush)
        {
            // Proceed only if we have a valid site collection reference.
            if (this.Site != null)
            {
                // Basically, we need to set or clear a flag on the RootWeb of the site collection.
                // This flag will tell the flushing timer job (running on the web application
                // with the farm service account credentials within OWSTIMER) whether or not
                // a flush is being requested.  If a flush is being requested (enableFlush is
                // TRUE), the timer job will then update the SPWebApplication properties 
                // (which it has the rights to modify) and clear this flag to indicate
                // that the flush was performed.
                SPWeb rootWeb = this.Site.RootWeb;
                rootWeb.Properties[res.ROOTWEB_PROPERTY_FLUSHFLAG] = enableFlush.ToString(CultureInfo.InvariantCulture);
                rootWeb.Properties.Update();

                // Since the flush status has likely changed, we should make another pass at
                // updating the controls on the page.
                SetFormControlState(true);
            }
        }


        /// <summary>
        /// This method is responsible for setting the state of the controls on the form
        /// based on flush pending status.
        /// </summary>
        /// <param name="stateIsChanged">A boolean value that indicates whether or not the
        /// flush state has changed within the context of the current postback; TRUE if yes,
        /// FALSE if no.</param>
        private void SetFormControlState(Boolean stateIsChanged)
        {
            // We need to determine if a flush has been performed from
            // this code prior to now.  Query the property that we maintain on the
            // parent web application to see if it has a valid date/time.
            Object lastOperation = RetrieveWebAppPropertyValue(res.WEBAPP_PROPERTY_LASTFLUSH);
            if (lastOperation != null)
            {
                // A non-null reference means that something was assigned for a 
                // last operation date/time.  Try to parse it into a DateTime; if
                // successful, it'll be written to the appropriate form label.
                DateTime flushDateTime;
                if (DateTime.TryParse(lastOperation.ToString(), out flushDateTime))
                {
                    CheckSectionInputForm.LabelText = String.Format(CultureInfo.InvariantCulture,
                                                              res.FORMAT_PAGE_MSG_LAST_FLUSH,
                                                              flushDateTime.ToString());
                }
            }

            // Is a flush pending for the current site collection?  The answer to this
            // question will drive which controls are available and visible.
            if (IsFlushPending())
            {
                // A flush is pending.  We need to disable each of the controls in the
                // check section since the user shouldn't be able to request another
                // flush with one already pending.  We should also make sure that the
                // checkbox is checked for consistency.
                ResetBlobCacheCheckBox.Enabled = false;
                ResetBlobCacheCheckBox.Checked = true;

                // Make sure the controls in the stop/cancel section are visible and
                // that the OK button is disabled.
                StopSectionInputForm.Visible = true;
                OkButton.Enabled = false;
            }

            else
            {
                // A flush isn't pending.  Hide the stop section and make sure the
                // OK button is enabled.
                ResetBlobCacheCheckBox.Enabled = true;
                StopSectionInputForm.Visible = false;
                OkButton.Enabled = true;

                // If the flush state has changed in the context of this call and
                // a flush isn't pending, then we can go ahead and clear the checkbox
                // on the form.
                if (stateIsChanged)
                    ResetBlobCacheCheckBox.Checked = false;
            }
        }


        #endregion Methods (Private)


    }
}
